#include "OgreGLSLESLinkProgramManager.h"
#include "OgreGLSLESGpuProgram.h"
#include "OgreLogManager.h"
#include "OgreStringConverter.h"

namespace Ogre
{
	template<> GLSLESLinkProgramManager* Singleton<GLSLESLinkProgramManager>::ms_Singleton = 0;

	GLSLESLinkProgramManager* GLSLESLinkProgramManager::getSingletonPtr(void)
	{
		return ms_Singleton;
	}

	GLSLESLinkProgramManager& GLSLESLinkProgramManager::getSingleton(void)
	{
		assert(ms_Singleton);
		return (*ms_Singleton);
	}

	GLSLESLinkProgramManager::GLSLESLinkProgramManager(void) : mActiveVertexGpuProgram(NULL),
		mActiveFragmentGpuProgram(NULL), mActiveLinkProgram(NULL)
	{
		mTypeEnumMap.insert(StringToEnumMap::value_type("float", GL_FLOAT));
		mTypeEnumMap.insert(StringToEnumMap::value_type("vec2", GL_FLOAT_VEC2));
		mTypeEnumMap.insert(StringToEnumMap::value_type("vec3", GL_FLOAT_VEC3));
		mTypeEnumMap.insert(StringToEnumMap::value_type("vec4", GL_FLOAT_VEC4));
		mTypeEnumMap.insert(StringToEnumMap::value_type("sampler2D", GL_SAMPLER_2D));
		mTypeEnumMap.insert(StringToEnumMap::value_type("samplerCube", GL_SAMPLER_CUBE));
		mTypeEnumMap.insert(StringToEnumMap::value_type("int", GL_INT));
		mTypeEnumMap.insert(StringToEnumMap::value_type("ivec2", GL_INT_VEC2));
		mTypeEnumMap.insert(StringToEnumMap::value_type("ivec3", GL_INT_VEC3));
		mTypeEnumMap.insert(StringToEnumMap::value_type("ivec4", GL_INT_VEC4));
		mTypeEnumMap.insert(StringToEnumMap::value_type("mat2", GL_FLOAT_MAT2));
		mTypeEnumMap.insert(StringToEnumMap::value_type("mat3", GL_FLOAT_MAT3));
		mTypeEnumMap.insert(StringToEnumMap::value_type("mat4", GL_FLOAT_MAT4));
	}

	GLSLESLinkProgramManager::~GLSLESLinkProgramManager(void)
	{
		for (LinkProgramIterator currentProgram = mLinkPrograms.begin();
			currentProgram != mLinkPrograms.end(); ++currentProgram)
		{
			OGRE_DELETE currentProgram->second;
		}
	}

	GLSLESLinkProgram* GLSLESLinkProgramManager::getActiveLinkProgram(void)
	{
		if (mActiveLinkProgram)
			return mActiveLinkProgram;
		uint64 activeKey = 0;
		if (mActiveVertexGpuProgram)
		{
			activeKey = static_cast<uint64>(mActiveVertexGpuProgram->getProgramID()) << 32;
		}
		if (mActiveFragmentGpuProgram)
		{
			activeKey += static_cast<uint64>(mActiveFragmentGpuProgram->getProgramID());
		}

		if (activeKey > 0)
		{
			LinkProgramIterator programFound = mLinkPrograms.find(activeKey);
			if (programFound == mLinkPrograms.end())
			{
				mActiveLinkProgram = new GLSLESLinkProgram(mActiveVertexGpuProgram,mActiveFragmentGpuProgram);
				mLinkPrograms[activeKey] = mActiveLinkProgram;
			}
			else
			{
				mActiveLinkProgram = programFound->second;
			}
		}

		if (mActiveLinkProgram)
			mActiveLinkProgram->activate();

		return mActiveLinkProgram;
	}

	void GLSLESLinkProgramManager::setActiveFragmentShader(GLSLESGpuProgram* fragmentGpuProgram)
	{
		if (fragmentGpuProgram != mActiveFragmentGpuProgram)
		{
			mActiveFragmentGpuProgram = fragmentGpuProgram;
			mActiveLinkProgram = NULL;
			glUseProgram(0);
			GL_CHECK_ERROR;
		}
	}

	void GLSLESLinkProgramManager::setActiveVertexShader(GLSLESGpuProgram* vertexGpuProgram)
	{
		if (vertexGpuProgram != mActiveVertexGpuProgram)
		{
			mActiveVertexGpuProgram = vertexGpuProgram;
			mActiveLinkProgram = NULL;
			glUseProgram(0);
			GL_CHECK_ERROR;
		}
	}

	void GLSLESLinkProgramManager::completeDefInfo(GLenum gltype,
		GpuConstantDefinition& defToUpdate)
	{
		switch(gltype)
		{
		case GL_FLOAT:
			defToUpdate.constType = GCT_FLOAT1;
			break;
		case GL_FLOAT_VEC2:
			defToUpdate.constType = GCT_FLOAT2;
			break;
		case GL_FLOAT_VEC3:
			defToUpdate.constType = GCT_FLOAT3;
			break;
		case GL_FLOAT_VEC4:
			defToUpdate.constType = GCT_FLOAT4;
			break;
		case GL_SAMPLER_2D:
			defToUpdate.constType = GCT_SAMPLER2D;
			break;
		case GL_SAMPLER_CUBE:
			defToUpdate.constType = GCT_SAMPLERCUBE;
			break;
		case GL_INT:
			defToUpdate.constType = GCT_INT1;
			break;
		case GL_INT_VEC2:
			defToUpdate.constType = GCT_INT2;
			break;
		case GL_INT_VEC3:
			defToUpdate.constType = GCT_INT3;
			break;
		case GL_INT_VEC4:
			defToUpdate.constType = GCT_INT4;
			break;
		case GL_FLOAT_MAT2:
			defToUpdate.constType = GCT_MATRIX_2X2;
			break;
		case GL_FLOAT_MAT3:
			defToUpdate.constType = GCT_MATRIX_3X3;
			break;
		case GL_FLOAT_MAT4:
			defToUpdate.constType = GCT_MATRIX_4X4;
			break;
		default:
			defToUpdate.constType = GCT_UNKNOWN;
			break;
		}

		defToUpdate.elementSize = GpuConstantDefinition::getElementSize(defToUpdate.constType, false);
	}

	bool GLSLESLinkProgramManager::completeParamSource(
		const String& paramName,
		const GpuConstantDefinitionMap* vertexContantDefs,
		const GpuConstantDefinitionMap* fragmentConstantDefs,
		GLUniformReference& refToUpdate)
	{
		if (vertexContantDefs)
		{
			GpuConstantDefinitionMap::const_iterator parami =
				vertexConstantDefs->find(paramName);
			if (parami != vertexConstantDefs->end())
			{
				refToUpdate.mSourceProgType = GPT_VERTEX_PROGRAM;
				refToUpdate.mConstantDef = &(parami->second);
				return true;
			}
		}
		if (fragmentConstantDefs)
		{
			GpuConstantDefinitionMap::const_iterator parami =
				fragmentConstantDefs->find(paramName);
			if (parami != fragmentConstantDefs->end())
			{
				refToUpdate.mSourceProgType = GPT_FRAGMENT_PROGRAM;
				refToUpdate.mConstantDef = &(parami->second);
				return true;
			}
		}
		return false;
	}

	void GLSLESLinkProgramManager::extractUniforms(GLuint programObject,
		const GpuConstantDefinitionMap* vertexConstantDefs,
		const GpuConstantDefinitionMap* fragmentConstantDefs,
		GLUniformReferenceList& lists)
	{
		GLint uniformCount;

#define BUFFERSIZE 200
		char uniformName[BUFFERSIZE];
		GLUniformReference newGLUniformReference;
		glGetProgramiv(programObject, GL_ACTIVE_UNIFORMS, &uniformCount);
		GL_CHECK_ERROR;

		for (int index = 0; index < uniformCount; index++)
		{
			GLint arraySize;
			GLenum glType;
			glGetActiveUniform(programObject, index, BUFFERSIZE, NULL,
				&arraySize, &glType, uniformName);
			GL_CHECK_ERROR;

			newGLUniformReference.mLocation = glGetUniformLocation(programObject, uniformName);
			if (newGLUniformReference.mLocation >= 0)
			{
				String paramName = String(uniformName);
				String::size_type arrayStart = paramName.find("[");
				if (arrayStart != String::npos)
				{
					if (paramName.compare(arrayStart, paramName.size() - 1, "[0]") != 0) continue;
					paramName = paramName.substr(0, arrayStart);
				}

				bool foundSource = completeParamSource(paramName,
					vertexConstantDefs, fragmentConstantDefs, newGLUniformReference);

				if (foundSource)
				{
					assert(size_t(arraySize) == newGLUniformReference.mConstantDef->arraySize
						&& "GL doesn't agree with our array size!");
					list.push_back(newGLUniformReference);
				}
			}
		}
	}

	void GLSLESLinkProgramManager::extractConstantDefs(const String& src,
		GpuNameConstants& defs, const String& filename)
	{
		String line;
		String::size_type currPos = src.find("uniform");
		while (currPos != String::npos)
		{
			GpuConstantDefinition def;
			String paramName;

			bool inLargerString = false;
			if (currPos != 0)
			{
				char prev = src.at(currPos - 1);
				if (prev != ' ' && prev != '\t' && prev != '\r' && prev != '\n'
					&& prev != ';')
					inLargerString = true;
			}
			if (!inLargerString && currPos + 7 < src.size())
			{
				char next = src.at(currPos + 7);
				if (next != ' ' && next != '\t' && next != '\r' && next != '\n')
					inLargerString = true;
			}

			currPos += 7;

			if (!inLargerString)
			{
				String::size_type endPos = src.find(";", currPos);
				if (endPos == String::npos)
				{
					break;
				}

				line = src.substr(currPos, endPos - currPos);
				for (String::size_type sqp = line.find(" ["); sqp != String::npos;
					sqp = line.find(" ["))
					line.erase(sqp, 1);
				StringVector parts = StringUtil::split(line, ", \t\r\n");

				for (StringVector::iterator i = parts.begin(); i != parts.end(); ++i)
				{
					StringToEnumMap::iterator typei = mTypeEnumMap.find(*i);
					if (typei != mTypeEnumMap.end())
					{
						completeDefInfo(typei->second, def);
					}
					else
					{
						StringUtil::trim(*i);
						if (i->empty())
							continue;

						String::size_type arrayStart = i->find("[", 0);
						if (arrayStart != String::npos)
						{
							String name = i->substr(0, arrayStart);
							StringUtil::trim(name);
							if (!name.empty())
								paramName = name;

							String::size_type arrayEnd = i->find("]", arrayStart);
							String arrayDimTerm = i->substr(arrayStart + 1, arrayEnd - arrayStart - 1);
							StringUtil::trim(arrayDimTerm);

							def.arraySize = StringConverter::parseInt(arrayDimTerm);
						}
						else
						{
							paramName = *i;
							def.arraySize = 1;
						}

						if (def.constType == GCT_UNKNOWN)
						{
							LogManager::getSingleton().logMessage(
								"Problem parsing the following GLSL Uniform: '"
								+ line + "' in file " + filename);
							break;
						}

						def.logicalIndex = 0;
						if (def.isFloat())
						{
							def.physicalIndex = defs.floatBufferSize;
							defs.floatBufferSize += def.arraySize * def.elementSize;
						}
						else
						{
							def.physicalIndex = defs.intBufferSize;
							defs.intBufferSize += def.arraySize * def.elementSize;
						}

						defs.map.insert(GpuConstantDefinitionMap::value_type(paramName, def));
						defs.generateConstantDefinitionArrayEntries(paramName, def);
					}
				}
			}

			currPos = src.find("uniform", currPos);
		}
	}
}
